This document presents the full analysis of the User Study on the usability of LiquidJava.
To evaluate LiquidJava, we have developed a user study that combines the two types of tests to answer the following research questions:
The study was divided into X tasks, namely:
The study and its tasks was evaluated by a Ethics Commitee prior to its start, and all participants signed a participation agreement.
This study was designed to have 30 participants familiar with Java. Therefore, participants were recruited through social media channels, such as Twitter, Facebook and Instagram, and through direct contact via email.
The remaining of this document presents the results and the performed analysis.
The study sessions were all conducted through the Zoom video platform, given the restrictions imposed by the COVID-19 pandemic, and the participants used their own environments to complete the study tasks.
The participants were given a study guide with the questions and the answer sheet all integrated into a Google Form. All the tasks and steps that the participants needed to take were included in the form to reduce the interviewer bias.
Therefore the data gathered joins the answers given by the participants with the time measured by the interviewer. The answers that were not in english were automatically translated using Google Translate, and are identified with an * at the end of the answer.
from plot_utils import *
from excel_processing import *
answers_pd
| User | Answer | Group | Familiarity_Java | Familiarity_Refinements | Familiarity_Tests | Familiarity_LiquidJava | Insert the line that contains the error. | Correct_Find_Java_1 | What do you suggest to fix it? | ... | Add refinements to the method inRange(...) in Method.java, according to the javadoc. | Correct_Annotate_2 | Add the annotations to TrafficLight.java. The TestTLWrong.java must have an incorrect behaviour and TestTLCorrect.java must have a correct behaviour. | Correct_Annotate_3 | Correct_Annotate_Fields | How easy was it to add the Refinement Types annotations? | What did you enjoy the most while using LiquidJava? | What did you dislike the most while using LiquidJava? | Would you use LiquidJava in your projects? | If you have any other comments or suggestions leave them here. | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1.0 | very familiar | vaguely familiar | yes | no | if(n <= 1) | yes | if(n < 1) | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.StateRefinem... | yes | empty | 5.0 | Ease of refining parameters, which speeds up c... | Nothing.* | yes | I would add more practical examples.* |
| 1 | 2 | 3+4 | 1.0 | familiar | vaguely familiar | yes | no | if (n <= 1) | yes | if (n == 0) | ... | import repair.regen.specification.Refinement;\... | cc | import repair.regen.specification.Refinement;\... | yes | empty | 5.0 | Easy to understand in terms of semantics. The ... | Hard to understand without access to documenta... | yes | Errors during the session since I had the wron... |
| 2 | 7 | 7 | 1.0 | very familiar | vaguely familiar | yes | no | 9 | yes | if(n < 1) | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 5.0 | The simple syntax based on annotations | NaN | yes | Keep improving it! |
| 3 | 13 | 9 | 1.0 | familiar | not familiar | no | no | 9 | yes | if(n < 1) | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 4.0 | The refinements are not so difficult to implem... | The overs were a bit slow and I needed to save... | yes | NaN |
| 4 | 4 | 11 | 1.0 | very familiar | not familiar | yes | no | 9 | yes | if(n < 1) | ... | import repair.regen.specification.Refinement;\... | cc | import repair.regen.specification.Refinement;\... | yes | yes | 5.0 | It allows controlling the states of variables ... | Sometimes it can be difficult to understand th... | yes | NaN |
| 5 | 17 | 13 | 1.0 | very familiar | very familiar | yes | yes | 9 | yes | if(n <= 0) | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 4.0 | Errors appearing statically* | repetition in statesets (red(this) green(this)... | yes | Would you use LiquidJava in your projects? -> ... |
| 6 | 14 | 15 | 1.0 | very familiar | familiar | yes | no | if(n <= 1) | yes | if(n < 1) | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 5.0 | It is very simple and non-intrusive to add ref... | I did not have any particular thing that I did... | yes | Being able to target already existing librarie... |
| 7 | 12 | 17+24 | 1.0 | very familiar | vaguely familiar | yes | no | 9 if(n <= 1) | yes | if(n < 1) | ... | import repair.regen.specification.Refinement;\... | cc | import repair.regen.specification.Refinement;\... | yes | yes | 5.0 | The feedback on the code expected behavior whi... | The installation progress was not very user fr... | yes | I think that it would be helpful if the commun... |
| 8 | 23 | 20 | 1.0 | very familiar | not familiar | yes | no | if(n <= 1) | yes | if(n <= 0) | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 5.0 | Quite intuitive* | LiquidJava does not accept capitalized states* | yes | NaN |
| 9 | 15 | 21 | 1.0 | very familiar | vaguely familiar | yes | no | return 0; | no | return 1; | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 5.0 | Everything, a particular note on the staterefi... | nothing, it is quite straightforward to be used | yes | NaN |
| 10 | 26 | 23 | 1.0 | vaguely familiar | familiar | yes | no | 9 | yes | if(n <= 1) -> if(n <= 0) | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 5.0 | It helps to improve program logic and avoid fu... | putting the refinements to the initialization ... | yes | NaN |
| 11 | 19 | 26 | 1.0 | very familiar | vaguely familiar | yes | no | if(n <= 1) | yes | if(n < 0) | ... | @Refinement("_ >= a && _ <= b")\npublic static... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 5.0 | + 1 for how quickly the plugin detects incorr... | Nothing much to point out, I'll just leave som... | yes | NaN |
| 12 | 27 | 28 | 1.0 | familiar | vaguely familiar | no | no | line 9 | yes | line 9 - <= 0 | ... | import repair.regen.specification.Refinement;\... | cc | import repair.regen.specification.Refinement;\... | yes | empty | 5.0 | It gives us the possibility to prevent code er... | Nothing really | yes | Show in the website a way to add the extension... |
| 13 | 29 | 30 | 1.0 | vaguely familiar | not familiar | yes | no | if(n <= 1) | yes | if(n <= 0) | ... | import repair.regen.specification.Refinement;\... | cc | import repair.regen.specification.Refinement;\... | yes | yes | 5.0 | Highly useful and easy to use with proper docu... | Nothing | yes | NaN |
| 14 | 31 | 32 | 1.0 | very familiar | vaguely familiar | yes | no | 9 if(n <= 1) | yes | if(n < 1) | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 4.0 | Being able to use states to define the expecte... | Error messages (a major difficulty of programm... | yes | NaN |
| 15 | 3 | 2 | 2.0 | very familiar | vaguely familiar | yes | no | 11 | yes | return 1; else return fibonacci(n-1) ... | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 4.0 | Specification of states, because enforcing cor... | Some error messages are not straight to the po... | yes | This work reminded me of the PLAID language (C... |
| 16 | 5 | 6 | 2.0 | familiar | vaguely familiar | yes | no | 10 a 11 | yes | if(n <= 1) return 1; else ... | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 4.0 | Modelling state machines was very useful! | the usage of _ for the return value; usage of ... | yes | Add documentation to @refinement; Translate ja... |
| 17 | 8 | 8 | 2.0 | familiar | not familiar | yes | no | 10 e 11 | yes | if(n < 2) return n; | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 4.0 | Is simple to follow once you understand the co... | Makes Java more verbose | yes | NaN |
| 18 | 16 | 10 | 2.0 | familiar | vaguely familiar | yes | no | 11 | yes | return 1; | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 5.0 | Getting errors I wouldn't get otherwise is the... | Nothing comes to mind. It was very fluid and i... | yes | NaN |
| 19 | 18 | 12 | 2.0 | very familiar | familiar | yes | yes | 11 | yes | return 1; | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 5.0 | Based on the examples and the video, it is eas... | NaN | yes | NaN |
| 20 | 11 | 14 | 2.0 | familiar | vaguely familiar | yes | no | if(n <= 1) | no | if(n == 0 || n == 1) | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 4.0 | Ease of integration with other tools (in the c... | NaN | yes | Make state set creation work similar to enumer... |
| 21 | 20 | 16 | 2.0 | very familiar | not familiar | yes | no | NaN | empty | NaN | ... | import repair.regen.specification.Refinement;\... | cc | import repair.regen.specification.Refinement;\... | yes | empty | 4.0 | It is very useful to check if the method param... | The syntax is a little different than usual, b... | yes | NaN |
| 22 | 25 | 18 | 2.0 | very familiar | vaguely familiar | yes | no | return 0; | yes | return 1; | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 5.0 | It was useful and easy to use | not being able to correct multiple files at th... | yes | Download link in blue; adding instructions in ... |
| 23 | 21 | 19 | 2.0 | very familiar | not familiar | yes | no | return 0; | yes | return 1; | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 4.0 | The fact it reported un-compliances with the s... | NaN | yes | NaN |
| 24 | 10 | 22 | 2.0 | very familiar | very familiar | no | no | if(n <= 1) return 0; | yes | if(n <= 0) return 1; ... | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 5.0 | Syntax was quite clear; the examples and video... | Nothing to record (minor: some annotations can... | yes | Nothing to record. |
| 25 | 22 | 25 | 2.0 | familiar | not familiar | yes | no | return 0; | yes | return 1; | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 4.0 | Easy to find the errors in the code | Nothing | yes | Plugin for IntelliJ ;) |
| 26 | 24 | 27 | 2.0 | familiar | vaguely familiar | yes | no | NaN | empty | NaN | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 5.0 | Easy way to know if something is wrong before ... | Having to save the file every time I made a ch... | yes | NaN |
| 27 | 28 | 29 | 2.0 | familiar | vaguely familiar | yes | no | if(n <= 1) | no | if(n<=0) return 0; else if(n == 1) ret... | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | empty | 4.0 | Ghost variables for state control and variable... | nothing it's really intuitive and straightforward | yes | NaN |
| 28 | 30 | 31 | 2.0 | very familiar | familiar | no | yes | 11 | yes | return 1; | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 4.0 | There seems to be more control in the programs... | The concept of predicates for Refinements got ... | yes | NaN |
| 29 | 32 | 33 | 2.0 | familiar | vaguely familiar | yes | no | 11 | yes | change the return to 1 | ... | import repair.regen.specification.Refinement;\... | yes | import repair.regen.specification.Refinement;\... | yes | yes | 5.0 | Clear documentation and easy to use, given the... | The state transition is a bit harder to get bu... | yes | NaN |
30 rows × 49 columns
When registering to the study, all participants answered questions about their familiarity with:
From all participants, 17 declared to be Very Familiar with Java, 11 are Familiar with the language, and 2 are Vaguely Familiar. These last group of participants were still selected to the study since they had already used test cases and testing frameworks in Java, which adds to the knowledge necessary to participate in the study.
The vast majority of participants had used test cases and Java testing frameworks before, as expected. And 90% of participants never had contact with LiquidJava. The ones that answered Yes to this question attended previous talks about the project but had never used the capacities of the tool while programming.
Participants that had contact with LiquidJava also considered themselves Familiar or Very Familiar with Refinement Types.
from background_plots import *
The interviewer also inquired about the occupation of the participants, and the results are in the following table.
| Occupation/Job | #Participants |
|---|---|
| Business | 8 (26.7%) |
| Faculty | 6 (20.0%) |
| PhD Students | 5 (16.7%) |
| Master Students | 7 (23.3%) |
| Final-Year Bachelor Students | 4 (13.3%) |
This section presents the exercises used in each of the tasks of the study and the results obtained.
Since 90% of the participants had no previous contact with LiquidJava, and more than 80% were not familiar with Refinement Types, we wanted to understand if, without a prior explanation, the added specifications were intuitive to use. Thus, the study included a task with refinements examples that the participants needed to interpret and use in a correct and incorrect form. Specifically, we presented three code snippets with LiquidJava refinements with an increasing difficulty level and asked the participants to implement a correct and incorrect usage for each of the represented features.
Interpreting refinement in variable
n the first exercise, participants had to assign a correct and incorrect value to the variable x, which restricted the range of values to the Earth's surface temperature limits.
// 1 - Variable Refinement
@Refinement("-25 <= x && x <= 45")
int r;
Interpreting refinement in Method
The second exercise asked the participants to implement a correct and incorrect invocation of function1, where the second parameter depends on the first.
// 2 - Function/Method Refinement
@Refinement("_ >= 0")
public static double function1(@Refinement("a >= 0") double a, @Refinement("b >= a") double b){
return (a + b)/2;
}
Interpreting refinements as state transitions in class Finally, the last task presented a class protocol with three possible states and methods that modeled the object state, and the participants were asked to create a MyObj object and implement a correct and incorrect sequence of at least three invocations. The MyObj class aimed to represent a Vending Machine object with the three states sX, sY and sZ as Show Items, Item Selected and Paid, respectively. The anonymization of the states and the class name were intentional to make the participants try to understand the refinements instead of calling the methods according to their mental idea of how a vending machine works.
@StateSet({"sX", "sY", "sZ"})
public class MyObj {
@StateRefinement(to="sY(this)")
public MyObj() {}
@StateRefinement(from="sY(this)", to="sX(this)")
public void select(int number) {}
@StateRefinement(from="sX(this)", to="sZ(this)")
public void pay(int account) {}
@StateRefinement(from="sY(this)", to="sX(this)")
@StateRefinement(from="sZ(this)", to="sX(this)")
public void show() {}
}
from other_exercises import *
interpret_plot()
The plot shows the evaluation of the answers given by the participants. Each answer was classified as Correct if both the correct and incorrect usage of the specification were correct, Incorrect if at least one of the usages was incorrect, or Unanswered if the placeholder for answering was left blank.
In the variable assignment, 86.7% of the participants answered correctly. The remaining participants understood the error when the examples were explained and claimed that the error was a pure distraction and misread the logical operators. The invocation of the annotated method had only one incorrect answer (3.3%). For the sequential methods' invocations, that depended on the class protocol described using the @StateRefinements, 46.7% of the answers were correct, and the remaining amount was split into incorrect and blank answers, showing that this example is less intuitive and harder to understand without a prior explanation, but still comprehensible by almost half of the participants.
Overall, refinement annotations in variables and methods are intuitive and easy to understand. However, the annotation of classes and their methods with protocols is less intuitive, and, in half of the cases, the participants would need a previous explanation to use these annotations correctly.
This section joins the first and third tasks, both related to finding implementation errors in Java code with or without the help of refinements. The pair of tasks aim to validate if using Java with Liquid Types makes it easier and faster to detect and fix implementation errors in LiquidJava than in plain Java. To this end, we chose four exercises with implementation errors that the participants had to detect and fix. Firstly only using the plain Java code (Task 1: Find the error in plain Java) and then with the help of LiquidJava and its plugin for Visual Studio Code (Task 3 - Find the error in LiquidJava). Between both tasks, the participants had a small introduction to LiquidJava.
Each exercise had a plain Java version and a LiquidJava version with the same implementation errors to allow us to compare the number of participants that found and fixed the bug and the time taken in each version. The first group of participants started with exercises Sum and Socket while the second group started with the plain Java versions of the exercises Fibonacci and ArrayDeque. When moving to detect the errors using LiquidJava, the first group had the exercises Fibonacci and ArrayDeque whereas the second group got the exercises Sum and Socket. Therefore, one participant never used the same exercise in both tasks, avoiding tainting the second task with previous knowledge of the solution and allowing us to obtain plain Java baselines for every exercise. With this split, the maximum number of answers to each version is 15 since only half the participants viewed each exercise version.
In both Task 1 and Task 3, we gathered the time spent in each exercise and the given answers. The answers were then evaluated into one of four possible categories: Correct, Incorrect, Unanswered, and Compiler Correct. The last category, Compiler Correct, represents the answers that, despite not having any error detected by the LiquidJava compiler, are not utterly correct according to the exercise.
The sum exercise contains a recursive method that should implement the sum of all numbers between 0 and the given parameter. However, it contains a bug in the base case since the method returns 0 if the gotten parameter is 1. This exercise was inspired by the recursion example presented in Refinement Types: A Tutorial.
Java version
public class Test2 {
/**
* The sum of all numbers between 0 and n
* @param n
* @return a positive value that represents the sum of all numbers between 0 and n, or 0 if n is negative
*/
public static int sum(int n) {
if(n <= 1)///correct: (n < 1) or (n <= 0)
return 0;
else {
int t1 = sum(n-1);
return n + t1;
}
}
}
LiquidJava version Since the informal documentation does not specify any conditions to the parameter its refinement is omitted. However, it specifies the return conditions that can be simplified into the return refinement presented, using the same expression as the tutorial.
@RefinementAlias("Nat(int x) {x >= 0}")
public class Test2 {
/**
* The sum of all numbers between 0 and n
* @param n
* @return a positive value that represents the sum of all numbers between 0 and n, or 0 if n is negative
*/
@Refinement("Nat(_) && _ >= n")
public static int sum(int n) {
if(n <= 1)//correct: (n < 1) or (n <= 0)
return 0;
else {
int t1 = sum(n-1);
return n + t1;
}
}
}
from sfsa_plots import *
plots("sum", pd_together1, pd_together2, 1, "Answers to Sum", patches[:3])
It is possible to see that in the plain Java version, only one participant could not find and fix the bug, while the 14 others were able to find the error, and 13 correctly fixed it. On the other hand, in the LiquidJava version, every participant was able to locate the error. However, only 7 participants were able to fix it correctly, and the same number answered with Compiler Correct options.
The answers accepted as Correct adjusted the base case to one of the following versions: if(n <= 0) or if(n < 1). The answers considered Compiler Correct are the ones that silenced the compiler error but are not correct according to the informal specification. Among these answers, 6 out of the 7 participants changed the base case's return value, changing it to return 1, while the other participant answered return Math.abs(n).
The 46.7% of Compiler Correct answers suggests that the participants were more focused on silencing the compiler error than on correcting the program according to the informal specification.
Additionally, we may relate the 40% of return 1 answers with the Task 1 of these participants, which included the Fibonacci exercise where the correct fix was changing the return line to return 1. This line of thought might indicate that the participants were biased by the previous sections of the study and opted for the same answer as they used in the beginning.
time_plot("sum", pd_together1, pd_together2, 1, "Time spent in the Sum exercise")
Average Time PlainJava: 1900-01-01 00:04:29.540000 Average Time LiquidJava: 1900-01-01 00:03:32.333333333 LiquidJava was faster by: 0 days 00:00:57.206666667
<Figure size 432x288 with 0 Axes>

The time plot contains two similar distributions, with a shorter average time in the LiquidJava version. In minutes, the participants spent 4 minutes and 30 seconds on average finding and fixing the error in plain Java, whereas in LiquidJava, they spent an average of 3 minutes and 32 seconds, being faster by almost 1 minute.
This exercise presents a recursive implementation of the method fibonacci that computes the nth Fibonacci number. However, the code contains an implementation error in the base case of the recursion, where the starting values of F(0) = 1 and F(1) = 1 are not respected.
Both versions of code are presented bellow.
Java code version:
public class Test2 {
/**
* Computes the fibonacci of index n
* @param n The index of the required fibonnaci number
* @return The fibonacci nth number. The fibonacci sequence follows the formula Fn = Fn-1 + Fn-2 and has the starting values of F0 = 1 and F1 = 1
*/
public static int fibonacci(int n){
if(n <= 1)
return 0;//correct: change to 1
else
return fibonacci(n-1) + fibonacci(n-2);
}
}
LiquidJava code version:
@RefinementAlias("GreaterEqualThan(int x, int y) {x >= y}")
public class Test2 {
/**
* Computes the fibonacci of index n
* @param n The index of the required fibonnaci number
* @return The fibonacci nth number. The fibonacci sequence follows the formula Fn = Fn-1 + Fn-2 and has the starting values of F0 = 1 and F1 = 1
*/
@Refinement( "_ >= 1 && GreaterEqualThan(_, n)")
public static int fibonacci(@Refinement("Nat(n)") int n){
if(n <= 1)
return 0;//correct: change to 1
else
return fibonacci(n-1) + fibonacci(n-2);
}
}
plots("fib", pd_together2, pd_together1, 1, "Answers to Fibonacci", patches[:1]+patches[2:])
time_plot("fib", pd_together2, pd_together1, 1, "Time spent in the Fibonacci exercise")
Average Time PlainJava: 1900-01-01 00:02:52.580000 Average Time LiquidJava: 1900-01-01 00:03:22.673333333 LiquidJava was faster by: -1 days +23:59:29.906666667
<Figure size 432x288 with 0 Axes>

We can see that all the participants could find and fix the exercise in LiquidJava, whereas in plain Java, only 73,3\% found the error, and 66,7\% were able to fix it.
The answers accepted as Correct changed the return of the base case to return 1, and all the different answers were determined as incorrect since they did not fix the presented error. Additionally, in plain Java, 2 participants in total decided to skip this question without answering the possible location of the bug or proposing a fix.
The time spent on the Java version was, on average, smaller than the time spent in LiquidJava. The average time spent in the plain Java exercise was 2 minutes and 52 seconds, while the time spent on LiquidJava was 3 minutes and 22 seconds, reaching a difference of 30 seconds. This result suggests that participants are already used to Fibonacci's plain implementation given its popularity. When the new refinements were added, participants spent more time understanding the different sections of the code.
This exercise uses the Socket class from the external library java.net. The class was modeled with states and state transitions according to the informal documentation of the class and a client method createSocket(...) was created with incorrect usage of the invocations' order.
In this exercise, the error lies in the invocation of socket.sendUrgentData(90) before the socket is connected to a server.
Java version
public class Test2 {
public void createSocket(InetSocketAddress addr) throws IOException{
int port = 5000;
InetAddress inetAddress = InetAddress.getByName("localhost");
Socket socket = new Socket();
socket.bind(new InetSocketAddress(inetAddress, port));
socket.sendUrgentData(90);
socket.close();
}
}
The same client code was shown to participants in both Tasks 1 and 3. However, the code in Task 3 contained a file with the Socket class annotations of states and the allowed state transitions between the methods. Again, the informal documentation of the class from the library could be reached using the enabled Java plugin.
LiquidJava - External Refinements
@ExternalRefinementsFor("java.net.Socket")
@StateSet({"unconnected", "binded", "connected", "closed"})
public interface SocketRefinements {
@StateRefinement(to="unconnected(this)")
public void Socket();
@StateRefinement(from="unconnected(this)",to="binded(this)")
public void bind(SocketAddress add);
@StateRefinement(from="binded(this)", to="connected(this)")
public void connect(SocketAddress add);
@StateRefinement(from="binded(this)", to="connected(this)")
public void connect(SocketAddress add, int timeout);
@StateRefinement(from="connected(this)")
public void sendUrgentData(int n);
@StateRefinement(to="closed(this)")
public void close();
}
plots("socket", pd_together1, pd_together2, 2, "Answers to Socket", patches[:1]+patches[2:])
time_plot("socket", pd_together1, pd_together2, 2, "Time spent in the Socket exercise")
Average Time PlainJava: 1900-01-01 00:05:35.206666666 Average Time LiquidJava: 1900-01-01 00:04:42.993333333 LiquidJava was faster by: 0 days 00:00:52.213333333
<Figure size 432x288 with 0 Axes>

This exercise presented a program that uses the ArrayDeque class from the java.util library.
The class contains popular methods to add, remove, and retrieve elements from an ArrayDeque object.
These operations may depend on the number of elements present on the deque, and if those dependencies are not respected, exceptions will arise. With the Java plugin enabled on the IDE, participants were able to access the documentation of the class.
The exercise presents a program that includes sequential invocations of methods of ArrayDeque. However, the invocation p.getLast() on line 8 produces an exception since it is called when the object is empty.
Java version
public class Test2 {
public static void main(String[] args) throws IOException{
ArrayDeque<Integer> p = new ArrayDeque<>();
p.add(2);
p.remove();
p.offerFirst(6);
p.getLast();
p.remove();
p.getLast();
p.add(78);
p.add(8);
p.getFirst();
}
}
The code presented in the LiquidJava version, includes the client as before, and also a separate file with the modelling of ArrayDeque with the ghost variable size.
LiquidJava version - External Refinements
@ExternalRefinementsFor("java.util.ArrayDeque")
@Ghost("int size")
public interface ArrayDequeRefinements<E> {
public void ArrayDeque();
@StateRefinement(to="size(this) == (size(old(this)) + 1)")
public boolean add(E elem);
@StateRefinement(to="size(this) == (size(old(this)) + 1)")
public boolean offerFirst(E elem);
@StateRefinement(from="size(this) > 0", to = "size(this) == (size(old(this)))")
public E getFirst();
@StateRefinement(from="size(this) > 0", to = "size(this) == (size(old(this)))")
public E getLast();
@StateRefinement(from="size(this)> 0", to="size(this) == (size(old(this)) - 1)")
public void remove();
@StateRefinement(from="size(this)> 0", to="size(this) == (size(old(this)) - 1)")
public E pop();
@Refinement("_ == size(this)")
public int size();
@Refinement("_ == (size(this) <= 0)")
public boolean isEmpty();
}
plots("deque", pd_together2, pd_together1, 2, "Answers to ArrayDeque", patches[:1])
time_plot("deque", pd_together2, pd_together1, 2, "Time spent in the ArrayDeque exercise")
Average Time PlainJava: 1900-01-01 00:03:41.926666666 Average Time LiquidJava: 1900-01-01 00:02:56.406666666 LiquidJava was faster by: 0 days 00:00:45.520000
<Figure size 432x288 with 0 Axes>

All the participants could correctly find and fix the error in both plain Java and LiquidJava. There were multiple options to fix the code to prevent the raising of the exception; the accepted answers included removing p.getLast() on line 8, verifying if the queue is empty with if(!p.isEmpty) right before line 8, changing the line to p.peekLast(), among others.
The average time participants took in this exercise in LiquidJava (2 minutes and 56 seconds) was 45 seconds less than using plain Java (3 minutes and 41 seconds).
With the results gathered from Task 1 and Task 3, we can conclude that LiquidJava helped the participants find the bugs present in the code since the percentage of participants who found the bugs was always higher, or equal, in the LiquidJava version when compared to the plain Java version. LiquidJava also helped the participants fix the bugs according to the error information provided. However, the participants focused on silencing the errors, which led to some answers that were only considered Compiler Correct since the error was not totally fixed.
The task of finding and fixing the bugs was faster in LiquidJava in all but one exercise. The latter refers to the Fibonacci example, which may have had a shorter time because of its popularity and the fact that most developers are familiar with its plain Java version.
From all the exercises, the one that benefited the most from the LiquidJava version was the Socket client, where we moved from having only one participant finding the error in plain Java to 100% in LiquidJava, and from no participant fixing the bug to 100% fixing it. These percentages may indicate that LiquidJava is more useful when used in more complex programs, with classes that have protocols less-known by developers.
Overall, LiquidJava helped participants find and fix the bugs, and in some cases, it helped them do it faster.
We inquired the participants on the ease of intalling the plugin. The majority felt that it was very easy to install the plugin (5 band).
The 3 empty answers were from participants that used the interviewer's environment to complete the tasks due to one of two problems:
The participant that rated the installation 3, did not have the code command in terminal.
installation_plot()
In this task participants were asked to add LiquidJava annotations to the implemented code according to the informal documentation written in the program as comments. In this step, participants could use the website and the video to help writing the refinements.
Participants had to annotate programs with increasing order of difficulty. The first program relied only on the annotation of a variable with its bounds. The second program expected the annotation of a method by specifying the parameters and return refinements. Finally, the third program required the annotation of a class protocol and the class fields. For each program, we presented an example of a correct usage of the refinement and another example of its incorrect usage to help the developers test their refinements.
The participants shared their proposals for the annotation of each exercise, and we evaluated them with the four categories used in the previous section, of Correct, Incorrect, Unanswered and Compiler Correct.
There were three code snipets presented to the participants related to each of the exercises. The results are shown below.
annotation_plot()
Variable Annotation
/* A month needs to have a value between 1 and 12*/
int currentMonth;
currentMonth = 13; // Error
currentMonth = 5; // Correct
The first and more straightforward program is the annotation of a variable within a upper and lower bound. This would be accomplished with the annotation: @Refinement("currentMonth >= 1 && currentMonth <= 12 "). In the first exercise, all the participants used the website as a resource to look for the right syntax, and 100% of them annotated the variable correctly.
Method Annotation
/**
* Returns a value within the range
* @param a The minimum border
* @param b The maximum border, greater than a
* @return A value in the interval [a, b] (including the border values)
*/
public static int inRange(int a, int b) {
return a + 1;
}
...
inRange(10, 11); //Correct
inRange(10, 9); //Error
The method presented in the second exercise where participants should add a refinement to the second parameter, changing the signature of the method to public static int inRange(int a, @Refinement("b > a") int b), and refine the return type of the method, adding the refinement @Refinement("_ >= a && _ <= b") above the method's signature.
24 out of 30 participants were able to add the expected annotations leading to 80% of Correct answers. However, 20% only added the annotations to the parameter, silencing the example error but not completing the exercise in its totality, which lead to the Compiler Correct answers.
Class Annotation
public class TrafficLight {
private int r;
private int g;
private int b;
public TrafficLight() { r = 76; g = 187; b = 23; }
public void transitionToGreen() { r = 76; g = 187; b = 23; }
public void transitionToAmber() { r = 255; g = 120; b = 0; }
public void transitionToRed() { r = 230; g = 0; b = -1; }
}
//Correct Test - different file
TrafficLight tl = new TrafficLight();
tl.transitionToAmber();
tl.transitionToRed();
tl.transitionToGreen();
tl.transitionToAmber();
//Incorrect Test - different file
TrafficLight tl = new TrafficLight();
tl.transitionToAmber();
tl.transitionToRed();
tl.transitionToAmber();
tl.transitionToGreen();
For the last exercise we asked participants to "Annotate the class TrafficLight, that uses rgb values (between 0 and 255) to define the color of the light, and follows the protocol defined by the image [below]", as announced in the study guide. The evaluation of this exercise was split into two: the addition of the refinements to model the class protocol, and the specification on the class private fields.

100% of the participants correctly modeled the class by declaring the starting states and the state transitions allowed in each method. This percentage constitutes a significant increase in the understanding of the class protocols when compared to the first time participants tried to understand the protocol in Task 1, where half of the participants could not interpret the meaning of the annotations.
However, only 43.3% of the participants annotated the class fields, and the remaining participants did not add any refinement to them, leaving an incorrect assignment in the body of one class method. There might be several reasons for why participants did not annotate the class fields. A possible explanation is that they misinterpreted the exercise, not realising the need for these annotations, another possible explanation is that participants did not consider it important to add these annotations to the code.
After being introduced to the annotations, the participants had to evaluate the ease of adding annotations from 0 - Very Difficult to 5 - Very Easy. 60% of the participants considered that adding the annotations was Very Easy, while the remaining 40% considered the task Easy, leading us to conclude that the refinements are simple to add to implemented code.
easy_annotation_plot()
At the end of the tasks, we asked the participants about the overall experience of using LiquidJava using three questions:
The answers to each question were analysed using a qualitative coding approach. We used inductive coding to create the codes, which were then used to review all passages and to identify the main topics of each answer, leading to a cohesive and systematic view of the results.
About the aspects that participants enjoyed about LiquidJava, we found that participants mostly appreciated the error reporting, using state refinements to model objects and the intuitive and non-intrusive syntax.
The codes used and examples of the participants answers related to the codes are presented below.
Codes representing the answers
Error Reporting
State Refinements
Syntax
Plugin
Understandability
Useful
Resources
Flexibility
key_liked()
To the question of the LiquidJava aspects that participants disliked, only 26 participants reported answer. The remaining 4 participants did not identify any negative aspect, leaving the answering slot empty.
Of the 26 answers, 8 explicitly mentioned there was Nothing they did not like. The remaining negative aspects were about the syntax and some plugin features.
Codes representing the answers
Noting (8) - "Nothing, it is quite straightforward to be used"
Syntax (6)- "[in the state refinement] repetition of this", "the usage of _ for the return value"
Plugin (4) - "Improve the usability of the plugin -remove double quotes; - use auto-complete inside the refinements", "not being able to correct multiple files at the same time"
Not Intuitive (3)- "Hard to understand without access to documentation, mainly DFA protocols"
Error Messages(3) - "Some error messages are not straight to the point"
Verbose (3) -"Makes Java more verbose"
Other (2) -"The state transition is a bit harder to get but paired with the given documentation is fine.", "The installation process was not very user friendly"
key_disliked()
The last question of the study asked the participants if they would use LiquidJava in their projects, to which all the participants answered affirmatively. However, in the final suggestions, one participant declared that they would only use it in critical parts of the project, and two other participants stated that they are not currently using Java in any project but would like to have similar verifications in other programming languages.
would_use()
The major takeaways of the study can be summarized in the following points.
Interpretation of refinements -- Refinements in variables and methods are easy to understand without a prior explanation, and even though the features to model classes are not very intuitive at first, they are easy to understand with few resources, in this case the 4-minute video and the website with examples.
Detecting and fixing implementation errors in Java and in LiquidJava -- From Task 1 and 3, it is possible to assess that LiquidJava helped developers find the error present in the code. For fixing the bugs, LiquidJava helped in all but one case, since developers focused on silencing compiler errors disregarding the reasoning behind the changes applied. As for the time taken in each exercise, participants generally finished the LiquidJava exercises faster. The one outlier to this was the Fibonacci exercise, probably because it is a traditional algorithm that developers are used to seeing in its plain form and not with the annotations.
Best result for detecting and fixing error in LiquidJava -- From all exercises, the one that had most improvements while using LiquidJava was the Socket client, where no participant was able to find the error in plain Java but all participants were able to detect and fix the error using LiquidJava. This result might show that LiquidJava is more useful when applied to lesser-known classes and protocols than to mainstream classes or simple code.
Annotate Programs -- All participants were able to add refinements to variables and to model class protocols. Furthermore, 80% were able to add refinements to methods correctly (the remaining silenced the errors), and 43% were able to introduce refinements in class fields (the remaining participants left the answer blank). Participants also classified the annotation process as Easy or Very Easy. Thus, we can conclude that refinements are easy to add to the code to model the desired behaviour of programs.
Compiler Correct answers -- Having partial specifications on the code lead to some participants changing the code to respect the specification, and therefore pass the verification, disregarding the intent of the program. In the Sum exercise and the annotation of methods, participants gave answers correct according to the refinements but incorrect according to the informal documentation, producing the Compiler Correct category of answer classification. This result might show that partial specifications can mislead developers if they do not capture the meaning of the specification and the expected behavior of the program.
Would participants use LiquidJava -- The fact that we got all the desired participants to the study, already helps answering this question, since it shows that Java developers are open to participate in studies to discover new approaches to improve their code quality. However, the last question of the study is the one that gives us confidence that participants are open to using LiquidJava since we asked them if they would use it in their projects, and all participants answered affirmatively. Therefore, we are confident that participants find LiquidJava accessible for its gains and are ready to use new useful verification tools.